package com.Example.cert;

import com.utils.PemUtil;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.RFC4519Style;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.util.*;


/**
 * 批量生成RepChain节点jks，并将各个jks中的证书导入到myTrustStore.jks（信任列表）中，
 * 同时输出各个节点别名组成的字符串序列（RepChain 配置共识列表需要）。
 * @author zyf
 */
public class GenerateJksFiles {
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    // 证书序列号
    private static long serialNumber = System.currentTimeMillis();
//    private static StringBuilder consensus = new StringBuilder();
    // 存放别名
    private static List consensusList = new LinkedList();
    private static StringBuilder allConsensusBuilder = new StringBuilder();
    private static StringBuilder partialConsensusBuilder = new StringBuilder();


    public static void main(String[] args) throws Exception {

        // 新建目录
        File folder = new File("newJks");
        if (!folder.isDirectory()) {
            folder.mkdir();
        } else {
            FileUtils.cleanDirectory(folder);
        }

        // 在相应的目录下生成jks
        String dirName = folder.getPath() + File.separator;

        // 将含有前5个节点证书的trustStore复制到这个目录来
        File trustStoreSrcFile = new File("jks/mytruststore.jks");
        FileUtils.copyFileToDirectory(trustStoreSrcFile,folder);

        // 初始化 keyPairGenerator
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
        keyPairGenerator.initialize(new ECGenParameterSpec("secp256k1"));

        // 通过keyPair生成jks
        int number = 10;
        for (int i = 6; i < number; i++) {
            KeyPair keyPair = keyPairGenerator.genKeyPair();
            genJksFileByKeyPair(dirName, keyPair, i);
        }

         // 简单的文件过滤
//        FileFilter fileFilter = file -> file.getName().contains("node");

        // 只需要名字包含"node"的jks文件
        File[] files = folder.listFiles(file -> file.getName().contains("node"));

        // load信任列表
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        String trustStorePath = dirName + "mytruststore.jks";
        trustStore.load(new FileInputStream(trustStorePath), "changeme".toCharArray());

        // 将证书导入到信任列表里
        for (File file : Objects.requireNonNull(files)) {
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(new FileInputStream(file), "123".toCharArray());
            Enumeration enumeration = keyStore.aliases();
            if (enumeration.hasMoreElements()) {
                String alias = (String) enumeration.nextElement();
                if (trustStore.containsAlias(alias)) {
                    continue;
                }
                Certificate certificate = keyStore.getCertificate(alias);
                trustStore.setCertificateEntry(alias, certificate);
                // 同时生成证书
                PemUtil.exportToPemFile(dirName + alias + ".cer","CERTIFICATE",certificate.getEncoded());
            }
        }
        // 保存trustStore.jks
        trustStore.store(new FileOutputStream(trustStorePath), "changeme".toCharArray());

        // 输出“全部”共识别名列表
//        System.out.println(consensus.toString());
        consensusList.forEach(alias -> allConsensusBuilder.append(",").append('"').append(alias).append('"'));
        System.out.println(allConsensusBuilder.toString());

        // 输出“部分”共识别名列表
        consensusList.subList(0,2).forEach(alias -> partialConsensusBuilder.append(",").append('"').append(alias).append('"'));
        System.out.println(partialConsensusBuilder.toString());

    }

    /**
     * 通过keyPair生成jks文件
     *
     * @param keyPair 密钥对
     * @param number  编号
     * @throws Exception
     */
    public static void genJksFileByKeyPair(String dirName, KeyPair keyPair, int number) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("JKS");
        //生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 生成证书
        X509Certificate certificate = genV3Certificate("SHA1withECDSA", keyPair, String.valueOf(number));
        Certificate[] chain = {certificate};
        // 别名
        String alias = generateRandomNumber(18) + ".node" + number;
        // jks文件名
        String fileName = dirName + alias + ".jks";
        File jksFile = new File(fileName);
        if (!jksFile.exists()) {
            jksFile.createNewFile();
        }
        /*  密钥库初始化
            https://stackoverflow.com/questions/5312559/how-do-i-programmatically-create-a-new-keystore
            https://docs.oracle.com/javase/6/docs/api/java/security/KeyStore.html#load%28java.io.InputStream,%20char%5B%5D%29
            In order to create an empty keystore, or if the keystore cannot be initialized from a stream, pass null as the stream argument.

         */
        keyStore.load(null, "123".toCharArray());
        // 将key与certificate保存到keystore中
        keyStore.setKeyEntry(alias, privateKey, "123".toCharArray(), chain);
        FileOutputStream fileOutputStream = new FileOutputStream(new File(fileName));
        // 保存keyStore到输出流中
        keyStore.store(fileOutputStream, "123".toCharArray());
        fileOutputStream.close();
//        consensus.append(",").append('"').append(alias).append('"');
        consensusList.add(alias);
    }


    /**
     * 产生 n 位随机数
     *
     * @return
     */
    protected static long generateRandomNumber(int n) {
        if (n < 1) {
            throw new IllegalArgumentException("随机数位数必须大于0");
        }
        return (long) (Math.random() * 9 * Math.pow(10, n - 1)) + (long) Math.pow(10, n - 1);

    }

    /**
     * 若干小时后的日期
     *
     * @param hoursInFuture
     * @return
     */
    // 计算日期
    public static Date calculateDate(int hoursInFuture) {
        long secs = System.currentTimeMillis() / 1000;
        return new Date((secs + (hoursInFuture * 60 * 60)) * 1000);
    }

    /**
     * 计算序列号
     *
     * @return
     */
    public static BigInteger calculateSerialNumber() {
        return BigInteger.valueOf(serialNumber++);
    }


    /**
     * 自签名证书
     *
     * @param sigAlg  签名算法
     * @param keyPair 密钥对
     * @return
     * @throws CertificateException
     * @throws OperatorCreationException
     */
    public static X509Certificate genV3Certificate(String sigAlg, KeyPair keyPair, String commonNames) throws CertificateException, OperatorCreationException {
        X500NameBuilder x500Bldr = new X500NameBuilder(RFC4519Style.INSTANCE);
        X500Name x500Name =
                x500Bldr.addRDN(RFC4519Style.cn, commonNames)
                        .addRDN(RFC4519Style.ou, "iscas")
                        .addRDN(RFC4519Style.o, "repchain")
                        .addRDN(RFC4519Style.l, "bj")
                        .addRDN(RFC4519Style.st, "bj")
                        .addRDN(RFC4519Style.c, "cn")
                        .build();
        X509v3CertificateBuilder x509v3CertificateBuilder = new JcaX509v3CertificateBuilder(
                x500Name,
                calculateSerialNumber(),
                calculateDate(0),
                calculateDate(24 * 365),
                x500Name,
                keyPair.getPublic()
        );

        ContentSigner signer = new JcaContentSignerBuilder(sigAlg).setProvider("BC").build(keyPair.getPrivate());
        return new JcaX509CertificateConverter().setProvider("BC").getCertificate(x509v3CertificateBuilder.build(signer));
    }
}
